Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/scan/DiscoveryRankingEngine.kt bf929c20f274232e0eb8ddc19b2dc82403e99e5b (bf929c20) Text, 7.92 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.discovery.scan

Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.database.entity.DiscoveredNodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.DiscoveryPresetResultEntity

T8b949e/** Input bundle for ranking: a preset result together with its discovered nodes. */
Tff7b72data Tff7b72class T56d364PresetRankingInputTb4b4b4(
Tff7b72val Te6edf3presetResultTb4b4b4: Te6edf3DiscoveryPresetResultEntityTb4b4b4,
Tff7b72val Te6edf3discoveredNodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3DiscoveredNodeEntityTff7b72>Tb4b4b4,
Tb4b4b4)

T8b949e/** Per-criterion score breakdown for a ranked preset. */
Tff7b72data Tff7b72class T56d364RankingScoreBreakdownTb4b4b4(
T8b949e/** Criterion 1: unique discovered node count. */
Tff7b72val Te6edf3uniqueNodeCountTb4b4b4: Tffa657IntTb4b4b4,
T8b949e/** Criterion 2: neighbor-report diversity (direct + mesh neighbor count). */
Tff7b72val Te6edf3neighborDiversityTb4b4b4: Tffa657IntTb4b4b4,
T8b949e/** Criterion 3: non-duplicate packet count (numPacketsRx - numRxDupe). */
Tff7b72val Te6edf3nonDupePacketCountTb4b4b4: Tffa657IntTb4b4b4,
T8b949e/** Criterion 4a: median SNR across discovered nodes. */
Tff7b72val Te6edf3medianSnrTb4b4b4: Tffa657FloatTb4b4b4,
T8b949e/** Criterion 4b: median RSSI across discovered nodes (tiebreak within criterion 4). */
Tff7b72val Te6edf3medianRssiTb4b4b4: Tffa657IntTb4b4b4,
T8b949e/** Criterion 5: best known distance to a valid-position node (metres). */
Tff7b72val Te6edf3bestKnownDistanceTb4b4b4: Tffa657DoubleTb4b4b4,
T8b949e/** Criterion 6: failure/reconnect penalty (packet failure rate). */
Tff7b72val Te6edf3failurePenaltyTb4b4b4: Tffa657DoubleTb4b4b4,
Tb4b4b4)

T8b949e/** Output ranking for a single preset. */
Tff7b72data Tff7b72class T56d364PresetRankingTb4b4b4(
T8b949e/** 1-based rank (1 = best). Tied presets share the same rank. */
Tff7b72val Te6edf3rankTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3presetResultTb4b4b4: Te6edf3DiscoveryPresetResultEntityTb4b4b4,
Tff7b72val Te6edf3scoreBreakdownTb4b4b4: Te6edf3RankingScoreBreakdownTb4b4b4,
T8b949e/** True when this preset tied with at least one other after all 6 criteria. */
Tff7b72val Te6edf3isTiedTb4b4b4: Tffa657BooleanTb4b4b4,
Tb4b4b4)

T8b949e/**
* Deterministic 6-level heuristic ranking engine for discovery preset results.
*
* The ranking order (best-first) is:
* 1. Highest unique discovered node count
* 2. Highest neighbor-report diversity (direct + mesh neighbor mentions)
* 3. Highest non-duplicate packet count
* 4. Best median link quality (median SNR first, then median RSSI)
* 5. Greatest best-known distance to a valid-position node
* 6. Lowest failure / reconnect penalty
*
* If two presets still tie after all heuristics they are labelled as tied.
*/
Tf0883e@Single
Tff7b72class T56d364DiscoveryRankingEngine Tb4b4b4{

T8b949e/**
* Rank the given preset inputs best-to-worst using the 6-level heuristic.
*
* @return sorted list of [PresetRanking] (index 0 = best). Empty input yields empty output.
*/
Tff7b72fun Td2a8ffrankTb4b4b4(Te6edf3inputsTb4b4b4: Te6edf3ListTff7b72<Te6edf3PresetRankingInputTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PresetRankingTff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3inputsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Te6edf3emptyListTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3scored Tff7b72= Te6edf3inputsTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toScoredTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3sorted Tff7b72= Te6edf3scoredTb4b4b4.Te6edf3sortedWithTb4b4b4(Te6edf3RANKING_COMPARATORTb4b4b4)

Tff7b72return Te6edf3assignRanksTb4b4b4(Te6edf3sortedTb4b4b4)
Tb4b4b4}

T8b949e// ---- internal helpers ----

Tff7b72private Tff7b72data Tff7b72class T56d364ScoredPresetTb4b4b4(Tff7b72val Te6edf3presetResultTb4b4b4: Te6edf3DiscoveryPresetResultEntityTb4b4b4, Tff7b72val Te6edf3breakdownTb4b4b4: Te6edf3RankingScoreBreakdownTb4b4b4)

Tff7b72private Tff7b72fun Te6edf3PresetRankingInputTb4b4b4.Td2a8fftoScoredTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ScoredPreset Tb4b4b4{
Tff7b72val Te6edf3pr Tff7b72= Te6edf3presetResult
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3discoveredNodes

Tff7b72val Te6edf3snrValues Tff7b72= Te6edf3nodesTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3snr Tb4b4b4}Tb4b4b4.Te6edf3sortedTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3rssiValues Tff7b72= Te6edf3nodesTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3rssi Tb4b4b4}Tb4b4b4.Te6edf3sortedTb4b4b4(Tb4b4b4)

Tff7b72return Te6edf3ScoredPresetTb4b4b4(
Te6edf3presetResult Tff7b72= Te6edf3prTb4b4b4,
Te6edf3breakdown Tff7b72=
Te6edf3RankingScoreBreakdownTb4b4b4(
Te6edf3uniqueNodeCount Tff7b72= Te6edf3prTb4b4b4.Te6edf3uniqueNodesTb4b4b4,
Te6edf3neighborDiversity Tff7b72= Te6edf3prTb4b4b4.Te6edf3directNeighborCount Tff7b72+ Te6edf3prTb4b4b4.Te6edf3meshNeighborCountTb4b4b4,
Te6edf3nonDupePacketCount Tff7b72= Tb4b4b4(Te6edf3prTb4b4b4.Te6edf3numPacketsRx Tff7b72- Te6edf3prTb4b4b4.Te6edf3numRxDupeTb4b4b4)Tb4b4b4.Te6edf3coerceAtLeastTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3medianSnr Tff7b72= Te6edf3medianTb4b4b4(Te6edf3snrValuesTb4b4b4) Tb4b4b4{ Tffa657it Tb4b4b4}Tb4b4b4,
Te6edf3medianRssi Tff7b72= Te6edf3medianIntTb4b4b4(Te6edf3rssiValuesTb4b4b4)Tb4b4b4,
Te6edf3bestKnownDistance Tff7b72= Te6edf3nodesTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3distanceFromUser Tb4b4b4}Tb4b4b4.Te6edf3maxOrNullTb4b4b4(Tb4b4b4) Tff7b72?: T79c0ff0.0Tb4b4b4,
Te6edf3failurePenalty Tff7b72= Te6edf3prTb4b4b4.Te6edf3packetFailureRateTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffassignRanksTb4b4b4(Te6edf3sortedTb4b4b4: Te6edf3ListTff7b72<Te6edf3ScoredPresetTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PresetRankingTff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Te6edf3emptyListTb4b4b4(Tb4b4b4)

T8b949e// Detect tie groups: consecutive entries that compare as 0.
Tff7b72val Te6edf3tieFlags Tff7b72= Te6edf3BooleanArrayTb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3sizeTb4b4b4)
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in T79c0ff0 Te6edf3until Te6edf3sortedTb4b4b4.Te6edf3size Tff7b72- T79c0ff1Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3RANKING_COMPARATORTb4b4b4.Te6edf3compareTb4b4b4(Te6edf3sortedTff7b72[Te6edf3iTff7b72]Tb4b4b4, Te6edf3sortedTff7b72[Te6edf3i Tff7b72+ T79c0ff1Tff7b72]Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3tieFlagsTff7b72[Te6edf3iTff7b72] Tff7b72= Tff7b72true
Te6edf3tieFlagsTff7b72[Te6edf3i Tff7b72+ T79c0ff1Tff7b72] Tff7b72= Tff7b72true
Tb4b4b4}
Tb4b4b4}

Tff7b72val Te6edf3result Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3PresetRankingTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3currentRank Tff7b72= T79c0ff1
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in Te6edf3sortedTb4b4b4.Te6edf3indicesTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3i Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3RANKING_COMPARATORTb4b4b4.Te6edf3compareTb4b4b4(Te6edf3sortedTff7b72[Te6edf3i Tff7b72- T79c0ff1Tff7b72]Tb4b4b4, Te6edf3sortedTff7b72[Te6edf3iTff7b72]Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3currentRank Tff7b72= Te6edf3i Tff7b72+ T79c0ff1
Tb4b4b4}
Te6edf3result Tff7b72+Tff7b72=
Te6edf3PresetRankingTb4b4b4(
Te6edf3rank Tff7b72= Te6edf3currentRankTb4b4b4,
Te6edf3presetResult Tff7b72= Te6edf3sortedTff7b72[Te6edf3iTff7b72]Tb4b4b4.Te6edf3presetResultTb4b4b4,
Te6edf3scoreBreakdown Tff7b72= Te6edf3sortedTff7b72[Te6edf3iTff7b72]Tb4b4b4.Te6edf3breakdownTb4b4b4,
Te6edf3isTied Tff7b72= Te6edf3tieFlagsTff7b72[Te6edf3iTff7b72]Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3result
Tb4b4b4}

Tff7b72companion Tff7b72object Tb4b4b4{
T8b949e/**
* Comparator implementing the 6-level heuristic (best-first ordering). "Higher is better" criteria use
* descending compare (b vs a). "Lower is better" criteria (penalty) use ascending compare (a vs b).
*/
Tff7b72private Tff7b72val Te6edf3RANKING_COMPARATOR Tff7b72=
Te6edf3ComparatorTff7b72<Te6edf3ScoredPresetTff7b72> Tb4b4b4{ Te6edf3aTb4b4b4, Te6edf3b Tff7b72-Tff7b72>
T8b949e// 1. Highest unique node count
Tff7b72var Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3uniqueNodeCountTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3uniqueNodeCountTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp

T8b949e// 2. Highest neighbor-report diversity
Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3neighborDiversityTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3neighborDiversityTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp

T8b949e// 3. Highest non-duplicate packet count
Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3nonDupePacketCountTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3nonDupePacketCountTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp

T8b949e// 4. Best median link quality: SNR first, then RSSI
Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3medianSnrTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3medianSnrTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp
Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3medianRssiTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3medianRssiTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp

T8b949e// 5. Greatest best-known distance
Te6edf3cmp Tff7b72= Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3bestKnownDistanceTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3bestKnownDistanceTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3cmp Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@Comparator Te6edf3cmp

T8b949e// 6. Lowest failure/reconnect penalty
Te6edf3aTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3failurePenaltyTb4b4b4.Te6edf3compareToTb4b4b4(Te6edf3bTb4b4b4.Te6edf3breakdownTb4b4b4.Te6edf3failurePenaltyTb4b4b4)
Tb4b4b4}

T8b949e/** Compute the median of a sorted float-convertible list. Returns 0 for empty. */
Tff7b72internal Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffmedianTb4b4b4(Te6edf3sortedTb4b4b4: Te6edf3ListTff7b72<Te6edf3TTff7b72>Tb4b4b4, Te6edf3toFloatTb4b4b4: Tb4b4b4(Te6edf3TTb4b4b4) Tff7b72-Tff7b72> Tffa657FloatTb4b4b4)Tb4b4b4: Tffa657Float Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return T79c0ff0f
Tff7b72val Te6edf3mid Tff7b72= Te6edf3sortedTb4b4b4.Te6edf3size Tff7b72/ T79c0ff2
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3size Tff7b72% T79c0ff2 Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3toFloatTb4b4b4(Te6edf3sortedTff7b72[Te6edf3mid Tff7b72- T79c0ff1Tff7b72]Tb4b4b4) Tff7b72+ Te6edf3toFloatTb4b4b4(Te6edf3sortedTff7b72[Te6edf3midTff7b72]Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2f
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3toFloatTb4b4b4(Te6edf3sortedTff7b72[Te6edf3midTff7b72]Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e/** Compute the median of a sorted Int list. Returns 0 for empty. */
Tff7b72private Tff7b72fun Td2a8ffmedianIntTb4b4b4(Te6edf3sortedTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return T79c0ff0
Tff7b72val Te6edf3mid Tff7b72= Te6edf3sortedTb4b4b4.Te6edf3size Tff7b72/ T79c0ff2
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3sortedTb4b4b4.Te6edf3size Tff7b72% T79c0ff2 Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3sortedTff7b72[Te6edf3mid Tff7b72- T79c0ff1Tff7b72] Tff7b72+ Te6edf3sortedTff7b72[Te6edf3midTff7b72]Tb4b4b4) Tff7b72/ T79c0ff2
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3sortedTff7b72[Te6edf3midTff7b72]
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.06s